import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_widgets_pc/config/pc_scroll_config.dart';

import 'kq_tab_bar.dart';

const Duration kqTabScrollDuration = Duration(milliseconds: 200);

/// KqTabBar + KqPageView 组合控件
///
/// @author 周卓
///
class KqTabBarPageView extends StatefulWidget {
  /// 指示器
  final IndicatorType indicatorType;

  /// 指示器大小
  final TabBarIndicatorSize? indicatorSize;

  /// 文字颜色和下划线颜色处理器，下划线颜色跟随选中的文字颜色，如果不设置，默认使用[_KqTabBarState._defaultTextColorBuilder]
  final Color? Function(IndicatorType indicatorType, bool selected)?
      textColorBuilder;

  /// Tab数据
  final List<TabDataEntity> tabs;

  /// tabBar 背景颜色
  final Color? tabBarBackgroundColor;

  /// pageView 背景颜色
  final Color? pageBackgroundColor;

  /// 右侧抽屉，在TabBar下方
  final Widget? endDrawer;

  /// 用于控制抽屉开关
  final GlobalKey<ScaffoldState>? drawerGlobalKey;

  /// 自定义TabBar右侧控件，比如管理Tab的菜单
  final Widget? customTabRightView;

  /// 自定义TabBar和KqPageView之间的自定义控件，比如搜素框
  final Widget? customTabBottomView;

  /// 页面
  final List<Widget> pages;

  /// TabBar是否可左右滚动，默认false
  final bool tabScrollable;

  /// KqPageView是否可左右滑动，默认true
  final bool pageScrollable;

  /// 默认页面下标，只能第一次赋值生效
  final int initialPageIndex;

  /// 页面改变回调
  final Function(int index, TabDataEntity entity)? onPageChanged;

  /// 这个是用来刷新控件用的，比如某个条件触发后需要改变控件的高度，大多数清空下用不到
  final KqTabBarPageViewController? controller;

  /// 底部分割线颜色
  final Color? dividerColor;

  final EdgeInsetsGeometry? labelPadding;

  final Function(int index, TapUpDetails details)? onRightTapTab;

  const KqTabBarPageView(
      {super.key,
      required this.tabs,
      required this.pages,
      this.controller,
      this.endDrawer,
      this.tabBarBackgroundColor,
      this.pageBackgroundColor,
      this.drawerGlobalKey,
      this.customTabRightView,
      this.customTabBottomView,
      this.labelPadding,
      this.indicatorType = IndicatorType.underline,
      this.indicatorSize,
      this.tabScrollable = false,
      this.pageScrollable = true,
      this.initialPageIndex = 0,
      this.textColorBuilder,
        this.onRightTapTab,
      this.dividerColor,
      this.onPageChanged});

  @override
  KqTabBarPageViewState createState() => KqTabBarPageViewState();
}

class KqTabBarPageViewController {
  KqTabBarPageViewState? _myState;

  int _pageIndex = 0;

  /// 当前页面
  int get pageIndex => _pageIndex;

  KqTabBarPageViewController({int pageIndex = 0}) {
    _pageIndex = pageIndex;
  }

  _bind(KqTabBarPageViewState state) {
    _myState = state;
  }

  void update() {
    _myState?.update();
  }

  /// 跳转到指定页面
  void jumpToPage(int page, {bool animate = true}) {
    _pageIndex = page;
    _myState?.jumpToPage(page, animate);
  }
}

class KqTabBarPageViewState extends State<KqTabBarPageView>
    with TickerProviderStateMixin {
  TabController? _tabController;

  int pageIndex = 0;

  @override
  void initState() {
    super.initState();
    widget.controller?._bind(this);
    _initData();
  }

  @override
  void didUpdateWidget(covariant KqTabBarPageView oldWidget) {
    super.didUpdateWidget(oldWidget);
    oldWidget.controller?._myState = null;
    widget.controller?._bind(this);
    if (oldWidget.tabs.length != widget.tabs.length ||
        widget.controller != oldWidget.controller) {
      _initData();
    }
  }

  void _onPageChanged(int index) {
    widget.onPageChanged?.call(index, widget.tabs[index]);
  }

  void _initData() {
    pageIndex = widget.controller?._pageIndex ?? pageIndex;
    if (pageIndex >= widget.tabs.length) {
      pageIndex = 0;
    }
    _tabController?.dispose();
    _tabController = TabController(
        initialIndex: pageIndex,
        length: widget.tabs.length,
        animationDuration: kqTabScrollDuration,
        vsync: this);
    _tabController?.addListener(() {
      if (!_tabController!.indexIsChanging) {
        _onPageChanged(_tabController!.index);
      }
    });
    if (widget.indicatorType == IndicatorType.roundBg) {
      for (var element in widget.tabs) {
        element.hideNumber = false;
      }
      widget.tabs[pageIndex].hideNumber = true;
    }
  }

  update() {
    setState(() {});
  }

  void jumpToPage(int page, bool animate) {
    if (animate) {
      _tabController?.animateTo(page);
    } else {
      _tabController?.index = page;
    }
  }

  @override
  void dispose() {
    _tabController?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            Expanded(child: PcScrollConfig(
              child: KqTabBar(
                indicatorType: widget.indicatorType,
                indicatorSize: widget.indicatorSize,
                items: widget.tabs,
                labelPadding: widget.labelPadding,
                tabController: _tabController,
                onRightTap: widget.onRightTapTab,
                dividerColor: widget.dividerColor,
                textColorBuilder: widget.textColorBuilder,
                backgroundColor: widget.tabBarBackgroundColor,
                scrollable: widget.tabScrollable,
                customTabRightView: widget.customTabRightView,
              ),
            ))
          ],
        ),
        widget.customTabBottomView == null
            ? Container()
            : widget.customTabBottomView!,
        Expanded(
            child: Scaffold(
          key: widget.drawerGlobalKey,
          endDrawer: widget.endDrawer,
          backgroundColor: widget.pageBackgroundColor,
          body: KqTabBarView(
            controller: _tabController,
            onPageChanged: (value) {
              if (pageIndex != value) {
                pageIndex = value;
                widget.controller?._pageIndex = value;
                _updateNumberVisible(value, true);
              }
            },
            physics: widget.pageScrollable
                ? const BouncingScrollPhysics()
                : const NeverScrollableScrollPhysics(),
            children: widget.pages,
          ),
        ))
      ],
    );
  }

  //更新角标是否可见
  void _updateNumberVisible(int value, bool update) {
    if (widget.indicatorType == IndicatorType.roundBg) {
      if (update) {
        setState(() {
          for (var element in widget.tabs) {
            element.hideNumber = false;
          }
          widget.tabs[value].hideNumber = true;
        });
      } else {
        for (var element in widget.tabs) {
          element.hideNumber = false;
        }
        widget.tabs[value].hideNumber = true;
      }
    }
  }
}

class KqTabBarView extends StatefulWidget {
  /// Creates a page view with one child per tab.
  ///
  /// The length of [children] must be the same as the [controller]'s length.
  const KqTabBarView({
    super.key,
    required this.children,
    this.controller,
    this.physics,
    this.onPageChanged,
    this.dragStartBehavior = DragStartBehavior.start,
    this.viewportFraction = 1.0,
    this.clipBehavior = Clip.hardEdge,
  })  : assert(children != null),
        assert(dragStartBehavior != null);

  /// This widget's selection and animation state.
  ///
  /// If [TabController] is not provided, then the value of [DefaultTabController.of]
  /// will be used.
  final TabController? controller;

  /// One widget per tab.
  ///
  /// Its length must match the length of the [TabBar.tabs]
  /// list, as well as the [controller]'s [TabController.length].
  final List<Widget> children;

  /// How the page view should respond to user input.
  ///
  /// For example, determines how the page view continues to animate after the
  /// user stops dragging the page view.
  ///
  /// The physics are modified to snap to page boundaries using
  /// [PageScrollPhysics] prior to being used.
  ///
  /// Defaults to matching platform conventions.
  final ScrollPhysics? physics;

  final ValueChanged<int>? onPageChanged;

  /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  final DragStartBehavior dragStartBehavior;

  /// {@macro flutter.widgets.pageview.viewportFraction}
  final double viewportFraction;

  /// {@macro flutter.material.Material.clipBehavior}
  ///
  /// Defaults to [Clip.hardEdge].
  final Clip clipBehavior;

  @override
  State<KqTabBarView> createState() => _KqTabBarViewState();
}

class _KqTabBarViewState extends State<KqTabBarView> {
  TabController? _controller;
  late PageController _pageController;
  late List<Widget> _children;
  late List<Widget> _childrenWithKey;
  int? _currentIndex;
  ValueChanged<int>? _onPageChanged;
  int _warpUnderwayCount = 0;
  bool _debugHasScheduledValidChildrenCountCheck = false;

  // If the KqTabBarView is rebuilt with a new tab controller, the caller should
  // dispose the old one. In that case the old controller's animation will be
  // null and should not be accessed.
  bool get _controllerIsValid => _controller?.animation != null;

  void _updateTabController() {
    final TabController? newController =
        widget.controller ?? DefaultTabController.of(context);
    assert(() {
      if (newController == null) {
        throw FlutterError(
          'No TabController for ${widget.runtimeType}.\n'
          'When creating a ${widget.runtimeType}, you must either provide an explicit '
          'TabController using the "controller" property, or you must ensure that there '
          'is a DefaultTabController above the ${widget.runtimeType}.\n'
          'In this case, there was neither an explicit controller nor a default controller.',
        );
      }
      return true;
    }());

    if (newController == _controller) {
      return;
    }

    if (_controllerIsValid) {
      _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
    }
    _controller = newController;
    if (_controller != null) {
      _controller!.animation!.addListener(_handleTabControllerAnimationTick);
    }
  }

  @override
  void initState() {
    super.initState();
    _updateChildren();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _updateTabController();
    _currentIndex = _controller!.index;
    _pageController = PageController(
      initialPage: _currentIndex!,
      viewportFraction: widget.viewportFraction,
    );
  }

  @override
  void didUpdateWidget(KqTabBarView oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.controller != oldWidget.controller) {
      _updateTabController();
      _currentIndex = _controller!.index;
      _warpUnderwayCount += 1;
      _pageController.jumpToPage(_currentIndex!);
      _warpUnderwayCount -= 1;
    }
    if (widget.children != oldWidget.children && _warpUnderwayCount == 0) {
      _updateChildren();
    }
  }

  @override
  void dispose() {
    if (_controllerIsValid) {
      _controller!.animation!.removeListener(_handleTabControllerAnimationTick);
    }
    _controller = null;
    // We don't own the _controller Animation, so it's not disposed here.
    super.dispose();
  }

  void _updateChildren() {
    _children = widget.children;
    _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
  }

  void _handleTabControllerAnimationTick() {
    if (_warpUnderwayCount > 0 || !_controller!.indexIsChanging) {
      return;
    } // This widget is driving the controller's animation.

    if (_controller!.index != _currentIndex) {
      _currentIndex = _controller!.index;
      _warpToCurrentIndex();
    }
  }

  Future<void> _warpToCurrentIndex() async {
    if (!mounted) {
      return Future<void>.value();
    }

    if (_pageController.page == _currentIndex!.toDouble()) {
      return Future<void>.value();
    }

    final Duration duration = _controller!.animationDuration;
    final int previousIndex = _controller!.previousIndex;

    if ((_currentIndex! - previousIndex).abs() == 1) {
      if (duration == Duration.zero) {
        _pageController.jumpToPage(_currentIndex!);
        return Future<void>.value();
      }
      _warpUnderwayCount += 1;
      await _pageController.animateToPage(_currentIndex!,
          duration: duration, curve: Curves.ease);
      _warpUnderwayCount -= 1;
      return Future<void>.value();
    }

    assert((_currentIndex! - previousIndex).abs() > 1);
    final int initialPage = _currentIndex! > previousIndex
        ? _currentIndex! - 1
        : _currentIndex! + 1;
    final List<Widget> originalChildren = _childrenWithKey;
    setState(() {
      _warpUnderwayCount += 1;

      _childrenWithKey = List<Widget>.of(_childrenWithKey, growable: false);
      final Widget temp = _childrenWithKey[initialPage];
      _childrenWithKey[initialPage] = _childrenWithKey[previousIndex];
      _childrenWithKey[previousIndex] = temp;
    });
    _pageController.jumpToPage(initialPage);

    if (duration == Duration.zero) {
      _pageController.jumpToPage(_currentIndex!);
      return Future<void>.value();
    }

    await _pageController.animateToPage(_currentIndex!,
        duration: duration, curve: Curves.ease);
    if (!mounted) {
      return Future<void>.value();
    }
    setState(() {
      _warpUnderwayCount -= 1;
      if (widget.children != _children) {
        _updateChildren();
      } else {
        _childrenWithKey = originalChildren;
      }
    });
  }

  // Called when the PageView scrolls
  bool _handleScrollNotification(ScrollNotification notification) {
    if (_warpUnderwayCount > 0) {
      return false;
    }

    if (notification.depth != 0) {
      return false;
    }

    _warpUnderwayCount += 1;
    if (notification is ScrollUpdateNotification &&
        !_controller!.indexIsChanging) {
      if ((_pageController.page! - _controller!.index).abs() > 1.0) {
        _controller!.index = _pageController.page!.round();
        _currentIndex = _controller!.index;
      }
      _controller!.offset =
          clampDouble(_pageController.page! - _controller!.index, -1.0, 1.0);
    } else if (notification is ScrollEndNotification) {
      _controller!.index = _pageController.page!.round();
      _currentIndex = _controller!.index;
      if (!_controller!.indexIsChanging) {
        _controller!.offset =
            clampDouble(_pageController.page! - _controller!.index, -1.0, 1.0);
      }
    }
    _warpUnderwayCount -= 1;

    return false;
  }

  bool _debugScheduleCheckHasValidChildrenCount() {
    if (_debugHasScheduledValidChildrenCountCheck) {
      return true;
    }
    WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
      _debugHasScheduledValidChildrenCountCheck = false;
      if (!mounted) {
        return;
      }
      assert(() {
        if (_controller!.length != widget.children.length) {
          throw FlutterError(
            "Controller's length property (${_controller!.length}) does not match the "
            "number of children (${widget.children.length}) present in KqTabBarView's children property.",
          );
        }
        return true;
      }());
    });
    _debugHasScheduledValidChildrenCountCheck = true;
    return true;
  }

  @override
  Widget build(BuildContext context) {
    assert(_debugScheduleCheckHasValidChildrenCount());

    return NotificationListener<ScrollNotification>(
      onNotification: _handleScrollNotification,
      child: PageView(
        dragStartBehavior: widget.dragStartBehavior,
        clipBehavior: widget.clipBehavior,
        controller: _pageController,
        onPageChanged: widget.onPageChanged,
        physics: widget.physics == null
            ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics())
            : const PageScrollPhysics().applyTo(widget.physics),
        children: _childrenWithKey,
      ),
    );
  }
}
